package fr.vvins.core.biz.user;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import fr.vvins.core.biz.auth.AuthManager;
import fr.vvins.core.biz.user.dto.CredentialDto;
import fr.vvins.core.biz.user.dto.FavoriteDto;
import fr.vvins.core.biz.user.dto.UserCommentDto;
import fr.vvins.core.biz.user.dto.UserCommunityDto;
import fr.vvins.core.biz.user.dto.UserContactDto;
import fr.vvins.core.biz.user.dto.UserDto;
import fr.vvins.core.biz.user.dto.UserDtoInfo;
import fr.vvins.core.biz.user.dto.UserLogDto;
import fr.vvins.core.catalog.entity.Vigneron;
import fr.vvins.core.catalog.entity.Vin;
import fr.vvins.core.catalog.entity.dao.ProductRepository;
import fr.vvins.core.catalog.entity.dao.VigneronRepository;
import fr.vvins.core.catalog.entity.dao.VinRepository;
import fr.vvins.core.common.TechnicalException;
import fr.vvins.core.common.dto.DtoFactory;
import fr.vvins.core.common.dto.DtoUtils;
import fr.vvins.core.common.dto.EntityDtoConverter;
import fr.vvins.core.jobs.JobException;
import fr.vvins.core.jobs.catalog.CatalogJobs;
import fr.vvins.core.user.dao.CommunityRepository;
import fr.vvins.core.user.dao.FavoriteRepository;
import fr.vvins.core.user.dao.UserLogRepository;
import fr.vvins.core.user.dao.UserRepository;
import fr.vvins.core.user.entity.Community;
import fr.vvins.core.user.entity.Favorite;
import fr.vvins.core.user.entity.User;
import fr.vvins.core.user.entity.UserCommunity;
import fr.vvins.core.user.entity.UserLog;

public class UserManagerImpl implements UserManager {
	
	private final static Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
	
	@Autowired
	private DtoFactory dtoFactory;
	@Autowired
	private EntityDtoConverter converter;
	@Autowired
	private UserRepository userDao;
	@Autowired
	private UserLogRepository userLogDao;
	@Autowired
	private VigneronRepository vigneronDao;
	@Autowired
	private ProductRepository productDao;
	@Autowired
	private VinRepository vinDao;
	@Autowired
	private FavoriteRepository favoriteDao;
	@Autowired
	private CommunityRepository communityDao;
	@Resource(name="catalogJobs")
	private CatalogJobs catalogJobs;
	@Resource(name="authManagers")
	private Map<String, AuthManager> authManagers;

	@Override
	@Transactional
	public Integer decrypt(String token) throws TechnicalException, UserException {
		try {
			// TODO : Review token management.
			log.debug("not yet implemented");
			return Integer.parseInt(token);
		} catch (NumberFormatException e) {
			throw new UserException(UserException.TOKEN_FORMAT_ERROR, e.getMessage());
		} 
	}
	
	@Override
	@Transactional
	public void addLog(Integer id, String entity, Integer entityId, Integer actionId) throws TechnicalException, UserException {
		UserLog ulog = new UserLog();
		try {
			ulog.setUser(getUserDao().findOne(id));
			ulog.setDate(new Date());
			if("Product".equals(entity) || "vin".equals(entity)){
				ulog.setProduct(productDao.findOne(entityId));
				catalogJobs.updateVinScore(entityId);
			} else if("vigneron".equals(entity)){
				ulog.setVigneron(vigneronDao.findOne(entityId));
				catalogJobs.updateVigneronScore(entityId);
			}
		} catch (JobException e) {
			throw new UserException(UserException.NOT_FOUND,e.getMessage());
		} catch (Throwable e) {
			throw new UserException(UserException.NOT_FOUND, "User not found");
		}
	}

	
	@Override
	public String crypt(Integer id) throws TechnicalException, UserException {
		log.debug("not yet implemented");
		// TODO : not yet implemented
		return id + "";
	}
	

	@Override
	@Transactional
	public UserDto createUser(UserDto dto) throws TechnicalException, UserException {
		User user = new User();
		BeanUtils.copyProperties(dto, user);
		try {
			user = getUserDao().save(user);
			BeanUtils.copyProperties(user, dto);
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE, e);
		}
		return dto;
	}

	@Override
	@Transactional
	public UserDto updateUser(Integer id, Map<String, String> property) throws TechnicalException, UserException {
		UserDto dto = new UserDto();
		try {
			User user = getUserDao().findOne(id);
			for(String key : property.keySet()){
				String value = property.get(key);
				DtoUtils.setPropertyValue(user, key, value);
			}
			getUserDao().save(user);
			converter.convertEntityToDto(user,dto);
		} catch (Throwable e) {
			throw new UserException(UserException.NOT_FOUND, "Le user " + id + " n'existe pas." );
		}
		return dto;
	}
	
	@Override
	@Transactional
	public UserDto authenticate(CredentialDto dto) throws TechnicalException, UserException {
		UserDto user = null;
		if(authManagers.containsKey(dto.getType())){
			AuthManager manager = authManagers.get(dto.getType());
			user = manager.authenticate(dto);
			if(user == null){
				throw new UserException(UserException.NOT_FOUND, "User not found");
			}
			return user;
		} else {
			throw new UserException(UserException.CREDENTIAL_TYPE, "Unknown credential type");
		}
	}
	
	@Override
	@Transactional
	public UserDto findUserByLogin(String login) throws TechnicalException, UserException {
		try {
			List<User> users =  getUserDao().findByLogin(login);
			if(users !=null && !users.isEmpty()){
				UserDto dto = new UserDto();
				converter.convertEntityToDto(users.get(0),dto);
				return dto;
			} 
			return null;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}
	
	@Override
	@Transactional
	public List<UserDtoInfo> findUsersByCommunityToken(List<String> tokens) throws TechnicalException, UserException {
		try {
			List<User> users =  getUserDao().findByCommunityTokens(tokens);
			if(users !=null && !users.isEmpty()){
				List<UserDtoInfo> dtos = new ArrayList<UserDtoInfo>();
				for(User u : users){
					UserDtoInfo dto = new UserDtoInfo();
					converter.convertEntityToDto(u,dto);
					dtos.add(dto);
				}
				return dtos;
			} 
			return null;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e);
		}
	}
	
	@Override
	@Transactional
	public UserDto findUserByCommunityToken(String token) throws TechnicalException, UserException {
		try {
			User user =  getUserDao().findByCommunityToken(token);
			if(user !=null){
				UserDto dto = new UserDto();
				converter.convertEntityToDto(user,dto);
				return dto;
			} 
			return null;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}
	
	@Override
	@Transactional
	public UserDto findUserById(Integer id) throws TechnicalException, UserException {
		try {
			User user =  getUserDao().findOne(id);
			if(user !=null ){
				UserDto dto = new UserDto();
				converter.convertEntityToDto(user,dto);
				return dto;
			} 
			return null;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e);
		}
	}
	
	@Override
	@Transactional
	public UserDtoInfo findUserInfoById(Integer id) throws TechnicalException, UserException {
		try {
			User user =  getUserDao().findOne(id);
			if(user !=null ){
				UserDtoInfo dto = new UserDtoInfo();
				converter.convertEntityToDto(user,dto);
				return dto;
			} 
			return null;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e);
		}
	}
	
	@Override
	@Transactional
	public UserDto findUserByEmail(String email) throws TechnicalException, UserException {
		try {
			User user =  getUserDao().findByEmail(email);
			if(user !=null){
				UserDto dto = new UserDto();
				converter.convertEntityToDto(user,dto);
				return dto;
			}  
			return null;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e);
		}
	}

	@Override
	@Transactional
	public List<UserLogDto> getLastLog(Integer userId) throws TechnicalException, UserException {
		try {
			List<UserLog> ulogs = getUserLogDao().findByUserId(userId);
			List<UserLogDto> dtos = new ArrayList<UserLogDto>();
			if(ulogs != null){
				for(UserLog ul : ulogs){
					dtos.add((UserLogDto) getConverter().convertEntityToDto(ul, new UserLogDto()));
				}
			}
			return dtos;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}

	@Override
	@Transactional
	public void addFavorite(Integer userId, String entityName, Integer entityId) throws TechnicalException, UserException {
		try {
			User u = getUserDao().findOne(userId);
			Favorite f = new Favorite();
			f.setUser(u);
			if("vin".equals(entityName)){
				Vin entity = getVinDao().findOne(entityId);
				if(entity == null){
					throw new UserException(UserException.ENTITY_EMPTY,"Entity " + entity + " : " + entityId + " must be exist.");
				}
				f.setProduct(entity);
			}
			if("vigneron".equals(entityName)){
				Vigneron entity = getVigneronDao().findOne(entityId);
				if(entity == null){
					throw new UserException(UserException.ENTITY_EMPTY,"Entity " + entity + " : " + entityId + " must be exist.");
				}
				f.setVigneron(entity);	
			}
			getFavoriteDao().save(f);
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}

	@Override
	@Transactional
	public List<FavoriteDto> getFavorites(Integer userId) throws TechnicalException, UserException {
		try {
			List<Favorite> favorites =  getFavoriteDao().findByUserId(userId);
			List<FavoriteDto> dtos = new ArrayList<FavoriteDto>();
			if(favorites !=null && !favorites.isEmpty()){	
				for( Favorite entity : favorites) {
					FavoriteDto dto = new FavoriteDto();
					dtos.add((FavoriteDto) converter.convertEntityToDto(entity,dto));				
				}
			} 
			return dtos;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}
	
	@Override
	@Transactional
	public List<FavoriteDto> getUserFavorites(Integer userId, Integer targetUserId) throws TechnicalException, UserException {
		try {
			User u = getUserDao().findOne(userId);
			// TODO check ?
			List<Favorite> favorites =  getFavoriteDao().findByUserId(targetUserId);
			List<FavoriteDto> dtos = new ArrayList<FavoriteDto>();
			if(favorites !=null && !favorites.isEmpty()){	
				for( Favorite entity : favorites) {
					FavoriteDto dto = new FavoriteDto();
					dtos.add((FavoriteDto) converter.convertEntityToDto(entity,dto));				
				}
			} 
			return dtos;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}

	@Override
	@Transactional
	public void removeFavorite(Integer userId, String entity, Integer entityId) throws TechnicalException, UserException {
		try {
			List<Favorite> favorites = null;
			if("vin".equals(entity)){
				favorites =  getFavoriteDao().findByProductId(userId, entityId);
			} else if("vigneron".equals(entity)){
				favorites =  getFavoriteDao().findByVigneronId(userId, entityId);
			} else {
				throw new UserException(UserException.ENTITY_BAD_TYPE, "The value " + entity + "is not valid");
			}
			if(favorites !=null && !favorites.isEmpty()){	
				for( Favorite f : favorites) {
					getFavoriteDao().delete(f);			
				}
			} 
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}
	

	@Override
	@Transactional
	public void addFollowing(Integer userId, Integer targetUserId) throws TechnicalException, UserException {
		try {
			User u = getUserDao().findOne(userId);
			User uf = getUserDao().findOne(targetUserId);
			Set<User> following = u.getFollowingUser();
			if(following == null){
				following = new HashSet<User>();
			}
			following.add(uf);
			u.setFollowingUser(following);
			getUserDao().save(u);
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}

	@Override
	@Transactional
	public void addFollowings(Integer userId, List<Integer> targetUsersId) throws TechnicalException, UserException {
		try {
			User u = getUserDao().findOne(userId);
			List<User> uf = getUserDao().findByUserIds(targetUsersId);
			Set<User> following = u.getFollowingUser();
			if(following == null){
				following = new HashSet<User>();
			}
			following.addAll(uf);
			u.setFollowingUser(following);
			getUserDao().save(u);
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}

	@Override
	public List<UserDtoInfo> getUserFollowers(Integer userId, Integer targetUserId) throws TechnicalException, UserException {
		try {
			List<User> followers =  getUserDao().findByFollowers(targetUserId);
			List<UserDtoInfo> followersUser = new ArrayList<UserDtoInfo>();
			if(followers != null){
				for(User u : followers){
					followersUser.add((UserDtoInfo) getConverter().convertEntityToDto(u, new UserDtoInfo()));
				}
			}
			return followersUser;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}
	
	@Override
	@Transactional
	public List<UserDtoInfo> getUserFollowing(Integer userId, Integer targetUserId) throws TechnicalException, UserException {
		try {
			User u = getUserDao().findOne(userId);
			// TODO: check user rights.
			User tu = getUserDao().findOne(targetUserId);
			List<UserDtoInfo> followingUser = new ArrayList<UserDtoInfo>();
			Set<User> following = tu.getFollowingUser();
			if(following != null){
				for(User uf : following){
					followingUser.add((UserDtoInfo) getConverter().convertEntityToDto(uf, new UserDtoInfo()));
				}
			}
			return followingUser;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}

	@Override
	@Transactional
	public void removeFollowing(Integer userId, Integer targetUserId) throws TechnicalException, UserException {
		try {
			User u = getUserDao().findOne(userId);
			User uf = getUserDao().findOne(targetUserId);
			Set<User> following = u.getFollowingUser();
			if(following == null){
				following = new HashSet<User>();
			}
			if(following.contains(uf)){
				following.remove(uf);
			}
			u.setFollowingUser(following);
			getUserDao().save(u);
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
		
	}

	@Override
	@Transactional
	public void addContactInfo(Integer tokenUserId, UserContactDto contact) throws TechnicalException, UserException {
		// TODO Auto-generated method stub
		
	}

	@Override
	@Transactional
	public List<UserContactDto> getContactInfo(Integer tokenUserId) throws TechnicalException, UserException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	@Transactional
	public void updateContactInfo(Integer tokenUserId, Map<String, String> properties) throws TechnicalException,
			UserException {
		// TODO Auto-generated method stub
		
	}

	@Override
	@Transactional
	public List<UserCommentDto> getUserComments(Integer userId, Integer targetUserId) throws TechnicalException,
			UserException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	@Transactional
	public List<UserCommentDto> getComments(Integer tokenUserId) throws TechnicalException, UserException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	@Transactional
	public List<UserDtoInfo> getFollowing(Integer tokenUserId) throws TechnicalException, UserException {
		try {
			List<User> following = getUserDao().findByFollowing(tokenUserId);
			List<UserDtoInfo> followingInfo = new ArrayList<UserDtoInfo>();
			if(following != null && following.size() > 0){
				for(User follower : following){
					followingInfo.add((UserDtoInfo) getConverter().convertEntityToDto(follower, new UserDtoInfo()));
				}
			}
			return followingInfo;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE, e);
		}
	}
	
	
	
	@Override
	@Transactional
	public List<UserDtoInfo> getFollowers(Integer tokenUserId) throws TechnicalException, UserException {
		try {
			List<User> followers = getUserDao().findByFollowers(tokenUserId);
			List<UserDtoInfo> followersInfo = new ArrayList<UserDtoInfo>();
			if(followers != null && followers.size() > 0){
				for(User follower : followers){
					followersInfo.add((UserDtoInfo) getConverter().convertEntityToDtoInfo(follower));
				}
			}
			return followersInfo;
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE, e);
		}
	}
	
	@Override
	public void addUserCommunity(Integer userId, UserCommunityDto community) throws TechnicalException, UserException {
		try {
			User u = getUserDao().findOne(userId);
			Community c = communityDao.findOne(community.getCommunity().getId());
			Set<UserCommunity> com = u.getUserCommunities();
			if(com == null){
				com = new HashSet<UserCommunity>();
			}
			UserCommunity uc = new UserCommunity();
			uc.setUserCommunityToken(community.getUserCommunityToken());
			uc.setUser(u);
			uc.setCommunity(c);
			com.add(uc);
			getUserDao().save(u);
		} catch (Throwable e) {
			throw new TechnicalException(TechnicalException.DATABASE,e.getMessage());
		}
	}
	
	public DtoFactory getDtoFactory() {
		return dtoFactory;
	}
	public void setDtoFactory(DtoFactory dtoFactory) {
		this.dtoFactory = dtoFactory;
	}
	public EntityDtoConverter getConverter() {
		return converter;
	}
	public void setConverter(EntityDtoConverter converter) {
		this.converter = converter;
	}

	public CatalogJobs getCatalogJobs() {
		return catalogJobs;
	}

	public void setCatalogJobs(CatalogJobs catalogJobs) {
		this.catalogJobs = catalogJobs;
	}

	public Map<String, AuthManager> getAuthManagers() {
		return authManagers;
	}

	public void setAuthManagers(Map<String, AuthManager> authManagers) {
		this.authManagers = authManagers;
	}

	public UserRepository getUserDao() {
		return userDao;
	}

	public void setUserDao(UserRepository userDao) {
		this.userDao = userDao;
	}

	public UserLogRepository getUserLogDao() {
		return userLogDao;
	}

	public void setUserLogDao(UserLogRepository userLogDao) {
		this.userLogDao = userLogDao;
	}

	public VigneronRepository getVigneronDao() {
		return vigneronDao;
	}

	public void setVigneronDao(VigneronRepository vigneronDao) {
		this.vigneronDao = vigneronDao;
	}

	public ProductRepository getProductDao() {
		return productDao;
	}

	public void setProductDao(ProductRepository productDao) {
		this.productDao = productDao;
	}

	public VinRepository getVinDao() {
		return vinDao;
	}

	public void setVinDao(VinRepository vinDao) {
		this.vinDao = vinDao;
	}

	public FavoriteRepository getFavoriteDao() {
		return favoriteDao;
	}

	public void setFavoriteDao(FavoriteRepository favoriteDao) {
		this.favoriteDao = favoriteDao;
	}

	public CommunityRepository getCommunityDao() {
		return communityDao;
	}

	public void setCommunityDao(CommunityRepository communityDao) {
		this.communityDao = communityDao;
	}


}
